package core.client;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import core.util.ErrorList;

public abstract class CoreClient<T> implements IConnectionSocket<T>{

	private Socket clientSocket = null;
	private InputStream is = null;
	private OutputStream os = null;
	private boolean isLoop = true;
	private long timeWait = 0;
	private long startTime = 0;
	private CheckIdleClient cic;
	
	public abstract byte[] encode(Object t);

	public abstract T decode();
	
	/**
	 * Xu ly su kien connect thanh cong
	 */
	public abstract void onConnect();
	
	/**
	 * Xu ly su kien disconnect thanh cong
	 */
	public abstract void onDisconnect();
	
	/**
	 * Xu ly su kien cho doi trong thoi gian dai
	 */
	public abstract void onIdle();
	
	/**
	 * Xu ly su kien khi co loi xay ra
	 * @param e
	 */
	public abstract void onError(Exception e, String errorType); 
	
	public abstract void onSent();
	
	/**
	 * Xu ly cong viec nhan du lieu
	 */
	public abstract void onReceive(T t);
	
	/**
	 * Cai dat cau hinh
	 */
	public abstract void onConfig();
	
	@Override
	public void config(){
		onConfig();
	}
	
	@Override
	public void connect(String serverAddress, int port){
		if (connectServer(serverAddress, port)) {
			config();
			runCheckIdle();
//			receive();
		}
	}
	
	@Override
	public void disconnect(){
		stopCheckIdle();
		stopReceive();
		if (clientSocket != null) {
			try {
				if (is != null) {
					is.close();
				}
				if (os != null) {
					os.close();
				}
				if (!clientSocket.isClosed()) {
					clientSocket.close();
					onDisconnect();
				}
			} catch (Exception e) {
				onError(e, ErrorList.E_DISCONNECT);
			}
		}
	}
	
	@Override
	public boolean send(Object t){
		byte[] bt = encode(t);
		if (bt == null) {
			return false;
		}
		return sendByte(os, bt);
	}
	
	@Override
	public void receive(){
		ClientReceive<T> ch = new ClientReceive<>(this);
		ch.start();
	}
	
	/**
	 * Gui du lieu len server
	 * 
	 * @param bt
	 * @throws IOException
	 */
	public boolean sendByte(OutputStream os, byte[] bt){
		try {
			if (os == null) {
				os = clientSocket.getOutputStream();
		}
			os.write(bt);
			os.flush();
		} catch (Exception e) {
			onError(e, ErrorList.E_WRITE);
			return false;
		}
		return true;
	}
	
	/**
	 * @return InputStream cua socket
	 */
	public InputStream getInputStream(){
		if (is == null) {
			try {
				is = clientSocket.getInputStream();
			} catch (IOException e) {
				onError(e, ErrorList.E_IOSTREAM);
			}
		}
		return is;
	}
	
	/**
	 * 
	 * @return OutputStream cua socket
	 */
	public OutputStream getOutputStream(){
		if (os != null) {
			try {
				os = clientSocket.getOutputStream();
			} catch (Exception e) {
				onError(e, ErrorList.E_IOSTREAM);
			}
		}
		return os;
	}
	
	/**
	 * Dat thoi gian cho doi cua client
	 * @param timeWait
	 */
	public void setTimeIdle(long timeWait){
		this.timeWait = timeWait;
	}
	
	public void stopCheckIdle(){
		if (cic != null) {
			cic.stopIdle();
		}
	}
	
	/**
	 * Dung viec chay client
	 */
	public void stopReceive(){
		isLoop = false;
		
	}
	
	public byte[] intByteOrder(byte[] inByte, ByteOrder byteOrder){
		int leng = inByte.length;
		int value = ByteBuffer.wrap(inByte).getInt();
		byte[] bt = null;
		ByteBuffer bb = ByteBuffer.allocate(leng).order(byteOrder);
		bt = new byte[leng];
		bb.putInt(value);
		bb.position(0);
		bb.get(bt);
		return bt;
	}
	
	public byte[] longByteOrder(byte[] inByte, ByteOrder byteOrder){
		int leng = inByte.length;
		long value = ByteBuffer.wrap(inByte).getLong();
		byte[] bt = null;
		ByteBuffer bb = ByteBuffer.allocate(leng).order(byteOrder);
		bt = new byte[leng];
		bb.putLong(value);
		bb.position(0);
		bb.get(bt);
		return bt;
	}
	
	public byte[] floatByteOrder(byte[] inByte, ByteOrder byteOrder){
		int leng = inByte.length;
		float value = ByteBuffer.wrap(inByte).getFloat();
		byte[] bt = null;
		ByteBuffer bb = ByteBuffer.allocate(leng).order(byteOrder);
		bt = new byte[leng];
		bb.putFloat(value);
		bb.position(0);
		bb.get(bt);
		return bt;
	}

	public byte[] doubleByteOrder(byte[] inByte, ByteOrder byteOrder){
		int leng = inByte.length;
		double value = ByteBuffer.wrap(inByte).getDouble();
		byte[] bt = null;
		ByteBuffer bb = ByteBuffer.allocate(leng).order(byteOrder);
		bt = new byte[leng];
		bb.putDouble(value);
		bb.position(0);
		bb.get(bt);
		return bt;
	}
	
	public byte[] charByteOrder(byte[] inByte, ByteOrder byteOrder){
		int leng = inByte.length;
		char value = ByteBuffer.wrap(inByte).getChar();
		byte[] bt = null;
		ByteBuffer bb = ByteBuffer.allocate(leng).order(byteOrder);
		bt = new byte[leng];
		bb.putChar(value);
		bb.position(0);
		bb.get(bt);
		return bt;
	}
	
	
	protected synchronized boolean isLoop(){
		return isLoop;
	}
	
	protected void setStartTime(long time){
		this.startTime = time;
	}

	protected synchronized boolean isWait(){
		long time = System.currentTimeMillis() - startTime;
		if (time > timeWait) {
			return false;
		}
		return true;
	}

	
	private void runCheckIdle(){
		if (timeWait <= 0) {
			return;
		}
		cic = new CheckIdleClient(this, timeWait);
		cic.start();
	}
	
	private boolean connectServer(String serverAddress, int port){
		if (clientSocket == null) {
			try {
				clientSocket = new Socket(serverAddress, port);
				onConnect();
			} catch (Exception e) {
				onError(e, ErrorList.E_CONNECTION);
				return false;
			}
		}
		return true;
	}

}
